/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package studio.raptor.ddal.core.connection;

import java.sql.SQLException;
import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import studio.raptor.ddal.common.exception.GenericException;
import studio.raptor.ddal.common.exception.code.CommonErrorCodes;
import studio.raptor.ddal.core.constants.DataSourceAccessLevel;

/**
 * 后端连接数据源。上层调用该类的{@link #getConnection()} 获取后端物理连接对象。
 *
 * @author Sam
 * @since 3.0.0
 */
public class BackendDataSource {

  private static Logger LOGGER = LoggerFactory.getLogger(BackendDataSource.class);
  private GenericObjectPool<BackendConnection> _pool;
  private BackendConnectionPoolConfig poolConfig;
  private boolean isReadOnlyPool = true;
  private final String dbInstanceName;
  private DataSourceAccessLevel accessLevel;
  /**
   * 创建后端连接池。 当前并没有实现对所有连接池参数的支持，配置是请留意文档。
   *
   * @param pooledObjectFactory 连接池对象工厂，实现自{@link org.apache.commons.pool2.PooledObjectFactory}。
   *                            提供池中连接对象的创建以及销毁操作。
   * @param poolConfig          连接池配置参数。包括最小空闲连接数，最大空闲连接数，最大连接数等，更多配置参数可以参考 {@link
   *                            org.apache.commons.pool2.impl.GenericObjectPoolConfig} 以及其父类 {@link
   *                            org.apache.commons.pool2.impl.BaseObjectPoolConfig}。
   */
  BackendDataSource(String dbInstanceName, DataSourceAccessLevel accessLevel,
      PooledBackendConnectionFactory pooledObjectFactory, BackendConnectionPoolConfig poolConfig) {
    this.dbInstanceName = dbInstanceName;
    this.poolConfig = poolConfig;
    this.accessLevel = accessLevel;

    AbandonedConfig ac = new AbandonedConfig();
    ac.setRemoveAbandonedOnBorrow(true);
    ac.setRemoveAbandonedOnMaintenance(true); // Eviction时处理abandonedConnection

    // 注意这里的第三个参数null
    _pool = new GenericObjectPool<>(pooledObjectFactory, poolConfig, ac);

    // borrow时都取的idleQueue的第一个元素。区别在当lifo时，归还的连接放在队列
    // 的尾部。相反fifo时，归还的连接放在队列的头部。 最直接的表象就是fifo相当
    // 于环形的遍历，池中每个对象被借出的几率都是均等的。
    _pool.setLifo(false);

    pooledObjectFactory.setBackendPoolConfig(this.poolConfig);
    try {
      _pool.preparePool();
    } catch (Exception e) {
      LOGGER.error("", e);
      throw new GenericException(CommonErrorCodes.COMMON_502);
    }
  }

  String getDbInstanceName() {
    return dbInstanceName;
  }

  public DataSourceAccessLevel getAccessLevel() {
    return accessLevel;
  }

  public void setAccessLevel(DataSourceAccessLevel accessLevel) {
    this.accessLevel = accessLevel;
  }

  /**
   * 是否只读连接Pool。此属性来自配置文件中db_instance的配置。
   *
   * @param readOnlyPool 只读连接池标记
   */
  void setReadOnlyPool(boolean readOnlyPool) {
    isReadOnlyPool = readOnlyPool;
  }


  /**
   * 当前对象对应配置文件中DataSourceGroup里的DataSource，有读写区别。
   *
   * @return 封装了物理连接的后端连接对象。
   * @throws SQLException 从池中获取连接异常
   */
  public BackendConnection getConnection() throws SQLException {

    BackendConnection bc;
    try {
      bc = _pool.borrowObject(poolConfig.getMaxWaitMillis());
    } catch (Exception borrowError) {
      throw new GenericException(CommonErrorCodes.COMMON_514);
    }
    if(null == bc) {
      throw new GenericException(CommonErrorCodes.COMMON_514);
    }
    // 设置连接的只读属性
    bc.setIsReadOnly(this.isReadOnlyPool);
    bc.setBelongingPool(_pool);
    return bc;
  }
}
